/*
 * Merger.cpp
 *
 *  Created on: 24/05/2013
 *      Author: jonathan
 */

#include "Merger.h"
#include "../preproceso/archivos/ArchivoAuxiliar.h"
#include "../preproceso/estructuras/EntradaAuxiliar.h"
#include "../commons/ArchivoUtils.h"
#include <cmath>
using namespace std;

Merger::Merger(FileName entrada, FileName preEntrada, FileName extEntrada,
		EnteroLargo cantidad) {
	this->entrada = entrada;
	this->prefEntrada = preEntrada;
	this->extEntrada = extEntrada;
	this->cantidad = cantidad;
}

EnteroLargo Merger::procesar() {
	EnteroLargo cantidadEtapa = sqrt(this->cantidad);
	EnteroLargo procesados = 0;
	EnteroLargo contador = 0;
	while (this->cantidad > procesados + cantidadEtapa) {
		this->procesarParcial(procesados, cantidadEtapa, this->prefEntrada,
				"etapa0-", contador);
		contador++;
		procesados += cantidadEtapa;
	}
	//proceso los archivos que quedan.
	this->procesarParcial(procesados, this->cantidad - procesados,
			this->prefEntrada, "etapa0-", contador);
	contador++;

	//proceso los archivos auxiliares para completar el merge
	this->procesarParcial(0, contador, "etapa0-", "merge", 0);
	//borro los archivos auxiliares...
	for (EnteroLargo i = 0; i < contador; i++) {
		removeFile(this->nombreSalida("etapa0-", i).c_str());
	}
	return 1;
}

string Merger::nombreEntrada(FileName prefijo, EnteroLargo nro) {
	string fn;
	fn.append(this->entrada);
	fn.append("/");
	fn.append(prefijo);

	fn.append(to_string(nro));
	fn.append(".");
	fn.append(this->extEntrada);
	return fn;
}

string Merger::nombreSalida(FileName prefijo, EnteroLargo nro) {
	string fn;
	fn.append(this->entrada);
	fn.append("/");
	fn.append(prefijo);
	fn.append(to_string(nro));
	fn.append(".");
	fn.append(this->extEntrada);
	return fn;
}

void Merger::procesarParcial(EnteroLargo inicial, EnteroLargo cantidad,
		FileName prefijoEnt, FileName prefijoSal, EnteroLargo nro) {
	//	EnteroLargo cont = 0;
	string directorioSalida = this->nombreSalida(prefijoSal, nro);
	ArchivoAuxiliar salida(directorioSalida.c_str(), ios_base::out);
	map<KeyMerge, ContadorMerge*> terminos;
	ArchivoAuxiliar * archivo= 0;
	ContadorMerge * cont = 0;
	string term;
	// leo la primer etapa
	for (EnteroLargo i = inicial; i < inicial + cantidad; i++) {
		archivo = new ArchivoAuxiliar(this->nombreEntrada(prefijoEnt, i).c_str(),
				ios_base::in);
		cont = new ContadorMerge(archivo);
		//se leen tantos registros de un archivo como archivos haya. dieron mejor las pruebas asi.
		this->hacerLectura(cont, cantidad, terminos);
	}
	//comienzo a pasar al archivo final.
	map<KeyMerge, ContadorMerge*>::iterator posicion;
	while (terminos.size() > 0) {
		//tomo el primer elemento, lo escribo al archivo y lo saco del mapa
		posicion = terminos.begin();
		cont = posicion->second;
		KeyMerge key = posicion->first;
		salida.escribir(key.getEntrada());
		delete (key.getEntrada());
		cont->operator--();
		terminos.erase(posicion);
		//si llegue al final para el documento
		if(cont->getCont() == 0){
			//si hay mas elementos  hago una lectura de ese archivo.
			if(cont->getArch()->hasNext()){
				this->hacerLectura(cont, cantidad, terminos);
			} else {
				// me deshago de archivo y contador.
				delete cont->getArch();
				delete cont;
			}
		}


	}
}


void Merger::hacerLectura(ContadorMerge* contador, EnteroLargo cantidadDeLecturas, map<KeyMerge,ContadorMerge*> &terminos){
	//leo lo que debo o puedo del archivo
	EntradaAuxiliar * entrada;
	ArchivoAuxiliar * archivo = contador->getArch();
	for(EnteroLargo i=0; i<cantidadDeLecturas && archivo->hasNext(); i++){
		entrada = archivo->next();
		++(*contador);
		terminos.insert(pair<KeyMerge, ContadorMerge*> (KeyMerge(entrada), contador));
	}

}
EnteroLargo KeyMerge::getDoc() const {
	return this->entrada->getDocumento();
}

string KeyMerge::getTermino() const {
	return this->entrada->getTermino();
}
bool KeyMerge::operator<(const KeyMerge& foo1) const {
	if (this->getTermino() == foo1.getTermino()) {
		return this->getDoc() < foo1.getDoc();
	}
	return this->getTermino() < foo1.getTermino();
}

KeyMerge::KeyMerge(EntradaAuxiliar * entrada) {
	this->entrada = entrada;
}

EntradaAuxiliar * KeyMerge::getEntrada(){
	return this->entrada;
}
ContadorMerge::ContadorMerge(ArchivoAuxiliar *aux) {
	this->cont = 0;
	this->arch = aux;
}

ArchivoAuxiliar *ContadorMerge::getArch() const {
	return arch;
}

EnteroLargo ContadorMerge::getCont() const {
	return cont;
}

void ContadorMerge::setArch(ArchivoAuxiliar *arch) {
	this->arch = arch;
}

ContadorMerge & ContadorMerge::operator ++() {
	this->cont++;
	return *this;
}

ContadorMerge & ContadorMerge::operator --() {
	this->cont--;
	return *this;
}

void ContadorMerge::setCont(EnteroLargo cont) {
	this->cont = cont;
}

ContadorMerge::~ContadorMerge() {

}
